package com.rayson.oneapi.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.rayson.oneapi.common.R;
import com.rayson.oneapi.gateway.model.InterfaceEntity;
import com.rayson.oneapi.gateway.service.InterfaceService;
import com.rayson.oneapi.gateway.util.URIUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;

/**
 * Author: rayson
 * Description:
 * Version: 1.0
 * Date: 2024-09-01 23:21
 */
@Slf4j
@Component
public class RouteModifyFilter implements GlobalFilter, Ordered {

    private static final Pattern URL_PATH_PATTERN = Pattern.compile("/api/\\d{6}");

    @Resource
    private InterfaceService interfaceService;

    @Resource
    private DiscoveryClient discoveryClient;

    private AtomicLong counter = new AtomicLong(0);


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // pringDiscoveryClient(discoveryClient);
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        log.info("请求路径为: {}", path);
        // 校验请求路径是否合法
        if (!checkPath(path)) {
            log.info("请求路径不合法: {}", path);
            return handleError(exchange, HttpStatus.BAD_REQUEST, JSON.toJSONString(R.error("request uri is illegal")));
        }
        // 提取接口号
        String interfaceNo = path.substring(5);
        // 根据接口号查询接口信息
        InterfaceEntity interfaceEntity = interfaceService.getInterfaceByInterfaceNumber(interfaceNo);
        // 校验接口是否存在且已上线
        if (Objects.isNull(interfaceEntity) || !interfaceEntity.getStatus().equals(3)) {
            log.info("请求接口不存在或还未上线: {}", interfaceNo);
            return handleError(exchange, HttpStatus.NOT_FOUND, JSON.toJSONString(R.error("interface not found or not online")));
        }
        // 校验请求方法是否正确
        if (!interfaceEntity.getHttpMethod().equals(request.getMethodValue())) {
            log.info("接口{}请求方法不正确: {}", interfaceNo, request.getMethodValue());
            return handleError(exchange, HttpStatus.METHOD_NOT_ALLOWED, JSON.toJSONString(R.error("request method not allowed")));
        }
        String originUrl = interfaceEntity.getOriginUrl();
        URI uri = URI.create(originUrl);
        String host = uri.getHost();
        // 如果url是服务名
        if (isService(host)) {
            // 根据服务名获取服务实例
            List<ServiceInstance> instances = discoveryClient.getInstances(host);
            if (instances.isEmpty()) {
                log.info("服务{}不存在", host);
                return handleError(exchange, HttpStatus.NOT_FOUND, JSON.toJSONString(R.error("service not found")));
            }
            // 轮询获取服务实例
            int index = (int) (counter.incrementAndGet() % instances.size());
            ServiceInstance instance = instances.get(index);
            // 获取服务实例的host和port
            String selectedHost = instance.getHost();
            int port = instance.getPort();
            // 拼接新的uri
            uri = URI.create("http://" + selectedHost + ":" + port + uri.getPath());
        }
        log.info("接口{}转发后的地址为: {}://{}:{}{}", interfaceNo, uri.getScheme(), uri.getHost(), uri.getPort(), uri.getPath());
        String redirectPath = uri.getPath();
        // 拼接url中的参数
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        redirectPath = concatQueryParams(redirectPath, queryParams);
        ServerHttpRequest newRequest = request.mutate().uri(URI.create(URIUtil.getUriExcludePath(uri))).path(redirectPath).build();
        exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
        return chain.filter(exchange.mutate().request(newRequest).build());
    }

    private boolean isService(String host) {
        List<String> services = discoveryClient.getServices();
        return services.contains(host);
    }

    @Override
    public int getOrder() {
        return 10001;
    }


    /**
     * 校验请求路径是否合法
     */
    private boolean checkPath(String path) {
        return URL_PATH_PATTERN.matcher(path).matches();
    }


    private Mono<Void> handleError(ServerWebExchange exchange, HttpStatus httpStatus, String errorResult) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(httpStatus);
        // 设置响应体
        byte[] responseBytes = errorResult.getBytes();
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        response.getHeaders().add("Content-Length", String.valueOf(responseBytes.length));
        DataBuffer buffer = response.bufferFactory().wrap(responseBytes);
        return response.writeWith(Mono.just(buffer));
    }

    private String concatQueryParams(String path, MultiValueMap<String, String> queryParams) {
        if (queryParams.isEmpty()) {
            return path;
        }
        String queryString = queryParams.toSingleValueMap().entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining("&"));
        return path + "?" + queryString;
    }

    private void pringDiscoveryClient(DiscoveryClient discoveryClient) {
        List<String> serviceIdList = discoveryClient.getServices();
        for (String serviceId : serviceIdList) {
            List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
            for (ServiceInstance instance : instances) {
                log.info("serviceId={},instanceId={},host={},port={},uri={},scheme={},metadata={}", instance.getServiceId(), instance.getInstanceId(), instance.getHost(), instance.getPort(), instance.getUri(), instance.getScheme(), instance.getMetadata());
            }
        }
    }

}
